#include "RenderContext.h"
#include "Buffer.h"
#include "Camera.h"
#include "Object.h"
#include "RenderTarget.h"
#include "SkyBox.h"
#include "Texture.h"
#include "Window.h"
#include "graphics/GLState.h"

using namespace Umikaze::Core;

std::shared_ptr<Buffer> RenderContext::getCameraData() {
  if (!_cameraBuffer) {
    _cameraBuffer = std::make_shared<Buffer>();
  }
  _cameraBuffer->set(&_cameraData, sizeof(CameraData));
  return _cameraBuffer;
}

void RenderContext::setCameraProperties(const std::shared_ptr<Camera> &camera) {
  _cameraData._viewMatrix = camera->getViewMatrix();
  _cameraData._projectionMatrix = camera->getProjMatrix();
  _cameraData._cameraPosition = camera->getPosition();
}

void RenderContext::setCameraProperties(const std::unique_ptr<Camera> &camera) {
  _cameraData._viewMatrix = camera->getViewMatrix();
  _cameraData._projectionMatrix = camera->getProjMatrix();
  _cameraData._cameraPosition = camera->getPosition();
}

void RenderContext::setViewAndProjectionMatrix(const glm::mat4 &view,
                                               const glm::mat4 &projection) {
  _cameraData._viewMatrix = view;
  _cameraData._projectionMatrix = projection;
  _cameraData._cameraPosition = glm::inverse(view) * glm::vec4(0, 0, 0, 1);
}

void RenderContext::setRenderTarget(
    const std::shared_ptr<RenderTarget> &renderTarget) {
  if (_renderTarget != renderTarget) {
    _renderTarget = renderTarget;
  }
}

void RenderContext::bindDefaultFbo() const {
  auto &state = GlState::instance();
  state->bindFramebuffer(0);
  state->viewport(0, 0, Window::instance()->getPixelWidth(),
                  Window::instance()->getPixelHeight());
}

glm::mat4 RenderContext::getViewMatrix() const {
  return _cameraData._viewMatrix;
}

glm::mat4 RenderContext::getProjMatrix() const {
  return _cameraData._projectionMatrix;
}

glm::vec3 RenderContext::getCameraPosition() const {
  return _cameraData._cameraPosition;
}

void RenderContext::setDepthTest(bool enable) { _enableDepthTest = enable; }

void RenderContext::setClearColor(float r, float g, float b, float a) {
  _r = r;
  _g = g;
  _b = b;
  _a = a;
}

void RenderContext::setCullFace(bool enable) { _enableCullFace = enable; }

// todo
void RenderContext::setCullFaceMode(uint32_t mode) { _cullMode = GL_BACK; }

void RenderContext::setStencilTest(bool enable) { _enableStencilTest = enable; }

void RenderContext::setClearBuffer(uint32_t bufferBit) {
  // TODO: 搞一个类似physx的结构出来
  _clearBit = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
}

void RenderContext::drawNormal() {}

void RenderContext::drawMotionVector(
    const std::vector<std::shared_ptr<Object>> &objects,
    const glm::mat4 &oldView, const glm::mat4 &oldProj) {
  if (_renderTarget) {
    _renderTarget->bind();
  } else {
    bindDefaultFbo();
  }
  auto &state = GlState::instance();
  _enableDepthTest ? state->enable(GL_DEPTH_TEST)
                   : state->disable(GL_DEPTH_TEST);

  _enableStencilTest ? state->enable(GL_STENCIL_TEST)
                     : state->disable(GL_STENCIL_TEST);

  _enableCullFace ? state->enable(GL_CULL_FACE) : state->disable(GL_CULL_FACE);
  glClearColor(0.5, 0.5, 0.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  for (auto &object : objects) {
    object->drawMotionVector(getCameraData(), oldView, oldProj);
  }
}

void RenderContext::drawDepth(
    const std::vector<std::shared_ptr<Object>> &objects) {
  if (_renderTarget) {
    _renderTarget->bind();
  } else {
    bindDefaultFbo();
  }
  auto &state = GlState::instance();
  state->enable(GL_DEPTH_TEST);
  _enableStencilTest ? state->enable(GL_STENCIL_TEST)
                     : state->disable(GL_STENCIL_TEST);

  _enableCullFace ? state->enable(GL_CULL_FACE) : state->disable(GL_CULL_FACE);

  state->cull(_cullMode);
  glClearColor(0.0, 0.0, 0.0, 1.0);
  glClear(_clearBit);
  glDrawBuffer(GL_NONE);
  glReadBuffer(GL_NONE);
  for (auto &object : objects) {
    object->drawDepth(getCameraData());
  }
}

void RenderContext::drawShadow(
    const std::vector<std::shared_ptr<Object>> &objects,
    const glm::mat4 &lightSpaceMatrix) {

  if (_renderTarget) {
    _renderTarget->bind();
  } else {
    bindDefaultFbo();
  }
  auto &state = GlState::instance();
  state->enable(GL_DEPTH_TEST);
  _enableStencilTest ? state->enable(GL_STENCIL_TEST)
                     : state->disable(GL_STENCIL_TEST);

  _enableCullFace ? state->enable(GL_CULL_FACE) : state->disable(GL_CULL_FACE);

  state->cull(_cullMode);
  glClearColor(0.0, 0.0, 0.0, 1.0);
  glClear(_clearBit);
  glDrawBuffer(GL_NONE);
  glReadBuffer(GL_NONE);
  for (auto &object : objects) {
    object->drawShadow(lightSpaceMatrix);
  }
}

void RenderContext::drawShadowCascade(
    const std::vector<std::shared_ptr<Object>> &objects,
    const std::shared_ptr<Buffer> &lightSpaceMatrices, uint32_t matrixCount) {
  if (_renderTarget) {
    _renderTarget->bind();
  } else {
    bindDefaultFbo();
  }

  auto &state = GlState::instance();
  glClearColor(0.0, 0.0, 0.0, 1.0);
  glClear(_clearBit);
  glDrawBuffer(GL_NONE);
  glReadBuffer(GL_NONE);

  for (auto &object : objects) {
    object->drawShadowCascade(lightSpaceMatrices, matrixCount);
  }
}

void RenderContext::draw(const std::vector<std::shared_ptr<Object>> &objects) {
  if (_renderTarget) {
    _renderTarget->bind();
  } else {
    bindDefaultFbo();
  }
  auto &state = GlState::instance();
  _enableDepthTest ? state->enable(GL_DEPTH_TEST)
                   : state->disable(GL_DEPTH_TEST);

  _enableStencilTest ? state->enable(GL_STENCIL_TEST)
                     : state->disable(GL_STENCIL_TEST);

  _enableCullFace ? state->enable(GL_CULL_FACE) : state->disable(GL_CULL_FACE);

  state->cull(_cullMode);
  glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
  glClear(_clearBit);

  for (auto &object : objects) {
    object->draw(getCameraData());
  }
};

void RenderContext::drawSkyBox(const std::shared_ptr<Texture> &skyBoxTexture) {
  if (_renderTarget) {
    _renderTarget->bind();
  } else {
    bindDefaultFbo();
  }

  auto &state = GlState::instance();
  state->setDepthFunc(GL_LEQUAL);
  state->enable(GL_DEPTH_TEST);
  state->disable(GL_STENCIL_TEST);
  state->disable(GL_CULL_FACE);
  auto view = glm::mat4(glm::mat3(getViewMatrix()));
  SkyBox::drawSkyBox(skyBoxTexture, view, getProjMatrix());
}
